﻿//
// Projet : Tourreau.Gilles.Tsl
// Description : Permet de réaliser des conversions de couleur
//  entre les domaines RVB et TSL.
//  
// Date : 05/05/2008
// Auteur : Gilles TOURREAU
// Site web : http://gilles.tourreau.fr
// Me contacter : http://gilles.tourreau.fr/me_contacter.html
//
// Modified to target WPF and english names.
//
using System;
using System.Windows.Media;

namespace XDCCManager
{
	/// <summary>
	/// Représente une couleur TSL (Teinte, Saturation, Luminosité)
	/// </summary>
	public struct HslColor
	{
		/// <summary>
		/// Maximum d'une couleur RVB
		/// </summary>
		private const int MaxRgb = 0xFF;
		/// <summary>
		/// Maximum d'une composante RVB
		/// </summary>
		private const int MaxRgbComponent = 255;
		/// <summary>
		/// Maximum d'une composante RVB multiplié par 2
		/// </summary>
		private const int MaxRgbComponentDouble = MaxRgbComponent * 2;

		/// <summary>
		/// Maximum d'une composante TSL
		/// </summary>
		private const int MaxHsl = 240;
		/// <summary>
		/// Maximum d'une composante TSL divisé par 2
		/// </summary>
		private const int MaxHslHalf = MaxHsl / 2;
		/// <summary>
		/// Maximum d'une composante TSL divisé par 3
		/// </summary>
		private const int MaxHslThird = MaxHsl / 3;
		/// <summary>
		/// Maximum d'une composante TSL multiplié par 3
		/// </summary>
		private const int MaxHslDouble = MaxHsl * 2;
		/// <summary>
		/// Maximum d'une composante TSL multiplié par 2 / 3
		/// </summary>
		private const int MaxHslTwoThirds = MaxHsl * 2 / 3;

		/// <summary>
		/// Créer une couleur TSL
		/// </summary>
		/// <param name="hue">Teinte de la couleur</param>
		/// <param name="saturation">Saturation de la couleur</param>
		/// <param name="lightness">Luminosité de la couleur</param>
		private HslColor(byte hue, byte saturation, byte lightness, byte alpha = 0) : this()
		{
			Hue = hue;
			Saturation = saturation;
			Lightness = lightness;
			Alpha = alpha;
		}

		/// <summary>
		/// Obtient la teinte de la couleur (compris entre 0 et 240)
		/// </summary>
		public byte Hue { get; private set; }
		/// <summary>
		/// Obtient la saturation de la couleur (compris entre 0 et 240)
		/// </summary>
		public byte Saturation { get; private set; }
		/// <summary>
		/// Obtient la luminosité de la couleur (compris entre 0 et 240)
		/// </summary>
		public byte Lightness { get; private set; }
		public byte Alpha { get; private set; }

		/// <summary>
		/// Créer une couleur TSL
		/// </summary>
		/// <param name="hue">Teinte de la couleur</param>
		/// <param name="saturation">Saturation de la couleur</param>
		/// <param name="lightness">Luminosité de la couleur</param>
		/// <exception cref="System.ArgumentException">Si <paramref name="hue"/>, 
		///     <paramref name="saturation"/> ou <paramref name="lightness"/> ne sont pas compris entre 0 et 240</exception>
		/// <returns>La nouvelle couleur TSL</returns>
		public static HslColor FromHsl(int hue, int saturation, int lightness)
		{
			if (hue < 0 || hue > 240)
				throw new ArgumentException("hue");
			if (saturation < 0 || saturation > 240)
				throw new ArgumentException("saturation");
			if (lightness < 0 || lightness > 240)
				throw new ArgumentException("lightness");

			return new HslColor(Convert.ToByte(hue), Convert.ToByte(saturation), Convert.ToByte(lightness));
		}

		public static HslColor FromHsla(int hue, int saturation, int lightness, byte alpha)
		{
			if (hue < 0 || hue > 240)
				throw new ArgumentException("hue");
			if (saturation < 0 || saturation > 240)
				throw new ArgumentException("saturation");
			if (lightness < 0 || lightness > 240)
				throw new ArgumentException("lightness");

			return new HslColor(Convert.ToByte(hue), Convert.ToByte(saturation), Convert.ToByte(lightness), alpha);
		}

		/// <summary>
		/// Converti la couleur TSL en couleur RVB.
		/// </summary>
		/// <returns>La couleur RVB convertie</returns>
		public Color ToColor()
		{
			int r = 0;
			int g = 0;
			int b = 0;

			int h = Hue;
			int s = Saturation;
			int l = Lightness;

			if (l != 0)
			{
				int temp;

				//1 - Si la saturation = 0 alors normaliser la luminosité par rapport au MaxRvb
				//et affecter cette normalisation aux compostantes R, V et B.
				if (s == 0)
				{
					temp = (l * MaxRgb) / MaxHsl;
					r = temp;
					g = temp;
					b = temp;
				}
				else
				{
					int temp2;

					//2 - Test de la luminosité
					if (l <= MaxHslHalf)
					{
						//Si la L < 0.5, temp = L * (1.0 + S)
						temp = ((l * (MaxHsl + s)) + MaxHslHalf) / MaxHsl;
					}
					else
					{
						//Sinon la L >= 0.5, temp = (L+S) - (L*S)
						temp = (l + s) - (((l * s) + 120) / 240);
					}

					//3 - Calcul de temp2 = 2.0 * L - temp
					temp2 = (2 * l) - temp;

					//4 - Pas de normalisation entre 0 et 1...

					//5.1 - Calculer pour chaque RGB :
					//R : TeinteR = T + 1/3
					//V : TeinteV = T
					//B : TeinteB = T - 1/3
					//Le reste du calcul se fait dans la méthode TeinteVersRVB,
					//afin de factoriser le calcul pour chaques composantes
					r = HueToRgb(temp, temp2, h + MaxHslThird);
					g = HueToRgb(temp, temp2, h);
					b = HueToRgb(temp, temp2, h - MaxHslThird);
				}
			}

			return Color.FromArgb(Alpha, (byte)r, (byte)g, (byte)b);
		}

		/// <summary>
		/// Méthode privée permettant de réaliser l'étape 5.2 du calcul de VersRgb()
		/// </summary>
		/// <param name="temp1">Variable temp1 de la méthode VersRgb()</param>
		/// <param name="temp2">Variable temp2 de la méthode VersRgb()</param>
		/// <param name="hue">Teinte de la méthode VersRgb()</param>
		/// <returns>La composante de la couleur calculée</returns>
		private static int HueToRgb(int temp1, int temp2, int hue)
		{
			int c;

			//5.2 - Si T < 0 alors T = T + 1.0
			if ((hue < 0) && ((hue + MaxHsl) >= hue))
			{
				hue += MaxHsl;
			}

			//5.2 - Si T > 1 alors T = T - 1.0
			if ((hue > MaxHsl) && ((hue - MaxHsl) <= hue))
			{
				hue -= MaxHsl;
			}

			//6 - Si 6.0 * T < 1 alors C = temp2 + (temp1 - temp2) * 6.0 * teinte
			if ((6 * hue) < MaxHsl)
			{
				c = temp2 + ((((temp1 - temp2) * hue) + 20) / 40);
			}
			else if ((2 * hue) < MaxHsl) //6 - Sinon si 2.0 * T < 1 alors C = temp1
			{
				c = temp1;
			}
			else if ((3 * hue) < 480) //6 - Sinon si 3.0 * T < 2 alors X = temp2 + (temp2 - temp2) * ((2.0 / 3.0) - teinte) * 6.0
			{
				c = temp2 + ((((temp1 - temp2) * (MaxHslTwoThirds - hue)) + 20) / 40);
			}
			else //6 - Sinon C = temp2
			{
				c = temp2;
			}

			//7 - Normaliser c sur MaxRvb
			return (((c * MaxRgb) + 120) / MaxHsl);
		}

		/// <summary>
		/// Créer une couleur TSL depuis une couleur RVB
		/// </summary>
		/// <param name="color">Couleur RVB</param>
		/// <returns>La couleur TSL correspondante à la couleur RVB</returns>
		public static HslColor FromRgb(Color color)
		{
			return FromRgba(color.R, color.G, color.B, color.A);
		}

		public static HslColor FromRgba(byte r, byte g, byte b, byte a)
		{
			HslColor color = FromRgb(r, g, b);
			color.Alpha = a;
			return color;
		}

		/// <summary>
		/// Créer une couleur TSL depuis une couleur RVB
		/// </summary>
		/// <param name="red">Composante rouge</param>
		/// <param name="green">Composante rouge</param>
		/// <param name="blue">Composante rouge</param>
		/// <exception cref="System.ArgumentException">Si un des paramètres <paramref name="red"/>,
		/// <paramref name="green"/> ou <paramref name="blue"/> n'est pas compris entre 0 et 255</exception>
		/// <returns>La couleur TSL correspondante à la couleur RVB</returns>
		public static HslColor FromRgb(int red, int green, int blue)
		{
			if (red < 0 || red > 255)
				throw new ArgumentException("red");
			if (green < 0 || green > 255)
				throw new ArgumentException("green");
			if (blue < 0 || blue > 255)
				throw new ArgumentException("blue");

			int max;
			int min;
			int h, s, l; //lum, pour ne pas confondre 1 et l !
			int r, g, b;
  
			//1 - Pas de normalisation...

			//2 - Trouver le maxi et le mini entre R, G et B
			max = Math.Max(Math.Max(red, green), blue);
			min = Math.Min(Math.Min(red, green), blue);

			//3 - L = (maxi + mini) / 2.0
			l = (((max + min) * MaxHsl) + MaxRgb) / MaxRgbComponentDouble;
            
			//4 - Si maxi = mini alors S = 0 et H = 0
			if (max == min)
			{
				return new HslColor(160, 0, Convert.ToByte(l));
			}

			//5 - Si L < 0.5 alors S = (maxi - mini) / (maxi + mini)
			if (l <= MaxHslHalf)
			{
				s = (((max - min) * MaxHsl) + ((max + min) / 2)) / (max + min);
			}
			else //5 - Sinon L >= 0.5 alors S = (maxi - mini) / (2.0 - maxi + mini)
			{
				s = (((max - min) * MaxHsl) + (((MaxRgbComponentDouble - max) - min) / 2)) / ((MaxRgbComponentDouble - max) - min);
			}

			//Avant 6 - Petite magouille pour normaliser avant les composants r, v, b
			r = (((max - red) * 40) + ((max - min) / 2)) / (max - min);
			g = (((max - green) * 40) + ((max - min) / 2)) / (max - min);
			b = (((max - blue) * 40) + ((max - min) / 2)) / (max - min);

			//6 - Si R = max alors, T = (V - B)
			if (red == max)
			{
				h = b - g;
			}
			else if (green == max) //6 - Sinon si G = max alors, T = 2.0 + (B - R)
			{
				h = (MaxHslThird + r) - b;
			}
			else //6 - Si (B est donc max), alors, T = 4.0 + (R - V)
			{
				h = (MaxHslTwoThirds + g) - r;
			}


			if (h < 0)
			{
				h += MaxHsl;
			}
			else if (h > MaxHsl)
			{
				h -= MaxHsl;
			}
            
			return new HslColor(Convert.ToByte(h), Convert.ToByte(s), Convert.ToByte(l));
		}
	}

	/// <summary>
	/// Classe statique contenant des méthodes d'extensions de la classe Color
	/// </summary>
	public static class ColorExtension
	{
		/// <summary>
		/// Ajoute une méthode à la classe Color permettant de prendre une couleur
		/// </summary>
		/// <param name="color"></param>
		/// <returns></returns>
		public static HslColor ToHslColor(this Color color)
		{
			return HslColor.FromRgb(color);
		}
	}

}
